package com.pet.system.service.impl;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.pet.system.common.HttpStatus;
import com.pet.system.common.ServiceException;
import com.pet.system.entity.LoginUser;
import com.pet.system.entity.User;
import com.pet.system.mapper.TokenMapper;
import com.pet.system.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Service
@Slf4j
public class TokenServiceImpl implements TokenService {
    @Autowired
    private TokenMapper tokenMapper;

    /**
     * 签名 此签名为 rayfoo 的16位 大写 MD5
     */
    private static final String SIGN_KEY = "5A1332068BA9FD17";

    /**
     * 默认的过期时间，300分钟
     */
    private static final Integer DEFAULT_EXPIRES = 60 * 300;

    /**
     * token默认的长度
     */
    private static final Integer DEFAULT_TOKEN_SIZE = 3;
    private static final Map<String, Long> LOGINMAP = new ConcurrentHashMap<>();
    @Override
    public Map<String, Long> getLoginCacheMap( )  {
        return LOGINMAP;
    }
    @Override
    public Long getLoginCacheToken(String token) throws Exception {
        Long logintoken = LOGINMAP.get(token);
        if (null != logintoken ) {
            log.info("获取token{}", logintoken);
            return logintoken;
        } else {
            throw new JWTDecodeException("无效的token！");
        }
    }

    @Override
    public String setLoginCacheToken(String token) {
        long currentTimeMillis = System.currentTimeMillis();
        Long expires = new Long(DEFAULT_EXPIRES * 1000);
        LOGINMAP.put(token, currentTimeMillis + expires);
        log.info("缓存token{}", token);
        return token;
    }

    @Override
    public void deleteLoginCacheToken(String token) {
        log.info("删除token{}", token);
        LOGINMAP.remove(token);
    }

    /**
     * 生成令牌
     *
     * @param map     数据正文
     * @param expires 过期时间，单位(秒)
     */
    @Override
    public String getToken(Map<String, String> map, Integer expires) throws Exception {

        //创建日历
        Calendar instance = Calendar.getInstance();
        //设置过期时间
        instance.add(Calendar.SECOND, expires);

        //创建jwt builder对象
        JWTCreator.Builder builder = JWT.create();

        //payload
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });

        //指定过期时间
        String token = builder.withExpiresAt(instance.getTime())
                //设置加密方式
                .sign(Algorithm.HMAC256(SIGN_KEY));
        //返回tokean
        return confoundPayload(token);
    }

    /**
     * 解析token
     *
     * @param token 输入混淆payload后的token
     */
    @Override
    public DecodedJWT verify(String token) throws Exception {
        //如果token无效
        if (token == null || "".equals(token)) {
            throw new JWTDecodeException("无效的token！");
        }
        //解析token
        String dToken = deConfoundPayload(token);
        //创建返回结果
        return JWT.require(Algorithm.HMAC256(SIGN_KEY)).build().verify(dToken);

    }

    @Override
    public String login(LoginUser loginUser) throws Exception {

        User login = tokenMapper.login(loginUser.getUsername(), loginUser.getPassword());
        if (null == login) {
            throw new ServiceException("用户名或密码错误！", HttpStatus.SUCCESS);
        }
        Map<String, String> map = new HashMap<>();
        map.put("username", login.getUsername());
        map.put("password", login.getPassword());
        map.put("id", login.getId() + "");
        map.put("phone", login.getPhone());
        map.put("sex", login.getSex() + "");
        map.put("role", login.getRole());
        map.put("nickname", login.getNickname());
        String token = getToken(map);
        String loginCacheToken = setLoginCacheToken(token);
        log.info("loginCacheToken {}", loginCacheToken);
        return token;
    }

    @Override
    public User getUser(String token) throws Exception {
        DecodedJWT decodedJWT = verify(token);
        Map<String, Claim> claims = decodedJWT.getClaims();
        User.UserBuilder builder = User.builder();
        builder.id(Long.valueOf(claims.get("id").asString()))
                .sex(Integer.parseInt(claims.get("sex").asString()))
                .nickname(claims.get("nickname").asString())
                .role(claims.get("role").asString())
                .phone(claims.get("phone").asString())
                .username(claims.get("username").asString())
                .password(claims.get("password").asString());
        return builder.build();
    }

    /**
     * 重载getToken 此方法为获取默认30分钟有效期的token
     *
     * @param map 数据正文
     */
    public String getToken(Map<String, String> map) throws Exception {
        return getToken(map, DEFAULT_EXPIRES);
    }


    /**
     * 对一个base64编码进行混淆  此处还可以进行replace混淆，考虑到效率问题，这里就不做啦~
     * 对于加密的思路还有位移、字符替换等~
     *
     * @param token 混淆payload前的token
     */
    private String confoundPayload(String token) throws Exception {
        //分割token
        String[] split = token.split("\\.");
        //如果token不符合规范
        if (split.length != DEFAULT_TOKEN_SIZE) {
            throw new JWTDecodeException("签名不正确");
        }
        //取出payload
        String payload = split[1];
        //获取长度
        int length = payload.length() / 2;
        //指定截取点
        int index = payload.length() % 2 != 0 ? length + 1 : length;
        //混淆处理后的token
        return split[0] + "." + reversePayload(payload, index) + "." + split[2];
    }

    /**
     * 对一个混淆后的base编码进行解析
     *
     * @param token 混淆后的token
     */
    private String deConfoundPayload(String token) throws Exception {
        //分割token
        String[] split = token.split("\\.");
        //如果token不符合规范
        if (split.length != DEFAULT_TOKEN_SIZE) {
            throw new JWTDecodeException("签名不正确");
        }
        //取出payload
        String payload = split[1];
        //返回解析后的token
        return split[0] + "." + reversePayload(payload, payload.length() / 2) + "." + split[2];
    }

    /**
     * 将md5编码位移
     *
     * @param payload payload编码
     * @param index   位移处
     */
    private String reversePayload(String payload, Integer index) {
        return payload.substring(index) + payload.substring(0, index);
    }

}
